package com.lewei.open.reject.repeated.submit.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.lewei.open.distributed.lock.core.DistributedLock;
import com.lewei.open.reject.repeated.submit.annotation.RejectRepeatedSubmit;
import com.lewei.open.reject.repeated.submit.exception.AnnotationUseException;
import com.lewei.open.reject.repeated.submit.type.RequestType;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;

/**
 * {@link RejectRepeatedSubmit}
 *
 * @author <a href="mailto:yins_emial@foxmail.com">yins</a>
 * @date 2021-11-08 18:34
 */
@Aspect
public class RejectRepeatedSubmitHandler {

    private final DistributedLock distributedLock;

    private static Logger log = LoggerFactory.getLogger(RejectRepeatedSubmitHandler.class);

    public RejectRepeatedSubmitHandler(DistributedLock distributedLock) {
        this.distributedLock = distributedLock;
    }

    @Pointcut("@annotation(com.lewei.open.reject.repeated.submit.annotation.RejectRepeatedSubmit)")
    public void pointCut() {
    }


    @Around("pointCut()")
    public Object invoke(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        String simpleName = signature.getDeclaringType().getName().replace(".", ":");
        String methodName = simpleName + ":" + method.getName();
        RejectRepeatedSubmit annotation = method.getAnnotation(RejectRepeatedSubmit.class);
        String uniqueKey = getUniqueKey(point.getArgs(), annotation, methodName);
        boolean lock = false;
        try {
            for (int retry = annotation.retry(); retry > 0; --retry) {
                lock = this.distributedLock.lock(uniqueKey, annotation.duration(), annotation.timeUnit());
                if (lock) {
                    break;
                }
            }
            if (lock) {
                return point.proceed();
            }
            throw new RuntimeException("The data is being processed, please try again later");
        } catch (Throwable e) {
            log.error("获取分布式锁异常", e);
            throw new RuntimeException(e);
        } finally {
            if (lock) {
                distributedLock.unlock(uniqueKey);
            }
        }
    }

    /**
     * 注解与参数配合获取唯一标识
     *
     * @param args       The method params
     * @param annotation {@link RejectRepeatedSubmit}
     * @param methodName
     * @return
     */
    private String getUniqueKey(Object[] args, RejectRepeatedSubmit annotation, String methodName) {
        String uniqueKey;
        if (RequestType.METHOD.equals(annotation.type()) || args.length < 1) {
            uniqueKey = methodName;
        } else if (args.length > 0) {
            int index = annotation.index();
            Object param = args[index];
            Class<?> paramClass = param.getClass();
            uniqueKey = methodName + ":" + processParams(annotation.filed(), param, paramClass);
        } else {
            throw new AnnotationUseException();
        }
        return uniqueKey;
    }


    /**
     * 处理参数
     *
     * @param filed      {@link RejectRepeatedSubmit#filed()}
     * @param param      The method param
     * @param paramClass param class type
     * @return 带全类名+方法名+唯一标识的键
     */
    private String processParams(String filed, Object param, Class paramClass) {
        String uniqueKey;
        // 处理参数 基本类型
        if (paramClass.isPrimitive()) {
            uniqueKey = String.valueOf(param);
        } else if (param instanceof String) {
            // String 类型
            uniqueKey = (String) param;
        } else if (param instanceof Number) {
            // 数值类型
            uniqueKey = String.valueOf(param);
        } else if (param instanceof Map) {
            uniqueKey = String.valueOf(((Map) param).get(filed));
        } else if (param instanceof Collection) {
            throw new AnnotationUseException();
        } else {
            // 对象
            Object jsonObject = JSON.toJSON(param);
            if (jsonObject instanceof JSONObject) {
                JSONObject json = (JSONObject) jsonObject;
                uniqueKey = String.valueOf(json.get(filed));
            } else {
                throw new AnnotationUseException();
            }
        }
        if (Objects.isNull(uniqueKey) || "".equals(uniqueKey) || "null".equals(uniqueKey)) {
            throw new AnnotationUseException();
        }
        return uniqueKey;
    }

}
